#!/bin/sh
#
# Copyright 2020, Data61, CSIRO (ABN 41 687 119 230)
#
# SPDX-License-Identifier: BSD-2-Clause
#

# Exit on unhandled error exit statuses; turn off filename expansion
# ("globbing"); warn on expansion of unset parameters.
set -efu

PROGNAME=${0##*/}
DIRNAME=${0%/*}

die () {
    echo "$PROGNAME: fatal error: $*" >&2
    exit 3
}

is_shell_script () {
    FILE=$1
    # Use parameter expansion tricks as a poor man's pattern matcher; if the
    # expansion does not mutate the parameter, then it _didn't_ match the
    # pattern.
    if [ "${FILE%.bash}" != "$FILE" ] \
        || [ "${FILE%.ksh}" != "$FILE" ] \
        || [ "${FILE%.sh}" != "$FILE" ]
    then
        # It's claiming to be a Bourne-based script; believe it.
        return 0
    fi

    # If it's executable, let file(1) do the hard work of figuring out what kind
    # of program it is.
    if [ -x "$FILE" ]
    then
        DESCRIPTION=$(file -b "$FILE")

        case "$DESCRIPTION" in
            # Catch POSIX, Korn, Bourne-Again, and Z shell scripts.
            ("* shell script*"|"* zsh script*")
                return 0
                ;;
            # Catch indirections through env.  Mind ksh88 and ksh93.
            ("*env *sh script*"|"*env ksh88 script*"|"*env ksh93 script*")
                return 0
                ;;
        esac
    fi

    # All checks failed; report that the argument is not a shell script.
    return 1
}

# Amusingly, this script won't validate itself with "command -v"; the
# "-v" flag was part of the "User Portability Utilities option" in POSIX
# Issue 6 (2004), but only promoted to full mandatory status in Issue 7
# (2017).  So instead use "which" (a Debianism, but widely available)
# until Debian's checkbashisms catches up.
#
#if ! command -v checkbashisms > /dev/null
if ! which checkbashisms > /dev/null
then
    die "\"checkbashisms\" command not found; is the \"devscripts\" package" \
        "installed?"
fi

if ! which file > /dev/null
then
    die "\"file\" command not found; is the \"file\" package installed?"
fi

if ! which python3 > /dev/null
then
    die "\"python3\" command not found; is the \"python3\" package installed?"
fi

if [ -f .stylefilter ]
then
    set -- $(python3 "$DIRNAME"/filter.py -f .stylefilter "$@")
fi

for FILE
do
    # Is the current file a shell script?  If not, skip it.
    is_shell_script "$FILE" || continue

    # --force checks for non-POSIX constructs even in scripts that declare bash
    # as the interpreter (useful because we don't want any bash scripts).
    #
    # --posix forces full-POSIX checking, ignoring the couple of exceptions to
    # POSIX-compliance permitted by Debian policy.
    #
    # --extra prints out the offending line after the diagnostic.
    if ! checkbashisms --force --posix --extra "$FILE"
    then
        die "script \"$FILE\" has non-POSIX features"
    fi

    # Now use the shell's own internal parser to find more subtle problems.
    # Even this is not perfect because in this mode, the shell fails to perform
    # many expansions due to possible side effects.  Also, the shell language is
    # not decidable.  :-|  (See Trienen, Jennerod, "Mining Debian Maintainer
    # Scripts",
    # https://debconf18.debconf.org/talks/90-mining-debian-maintainer-scripts/ )
    #
    # Here's an example of a construct sh -n doesn't catch that fails when run
    # for real:
    #
    # [[ a =~ a* ]] || echo a does not equal a
    #
    # ...and more forgivably, stupid eval tricks like this:
    #
    # STRING='${ARRAY_DEREF[1]}'; eval echo $STRING
    if ! sh -n "$FILE"
    then
        die "script \"$FILE\" failed syntax check"
    fi
done
